//
//  NDRefreshControl.m
//  JiMeiUnv
//
//  Created by devp on 14-9-4.
//  Copyright (c) 2014年 nd. All rights reserved.
//

#import "NDRefreshControl.h"

#import "NDCircleContainView.h"
#import "BlocksKit.h"
#import "EXTScope.h"

static NDRefreshControl *sSelf = nil;

typedef NS_ENUM(NSInteger, NDRefreshControlState) {
    NDRefreshControlStateDragging,
	NDRefreshControlStateLoading,
};

static const CGFloat NDRefreshControlDefaultThreshold     =       50.f;
static const CGFloat NDRefreshControlDefaultHeight        =       NDRefreshControlDefaultThreshold;

@interface NDRefreshControl () {
    struct {
        unsigned int delegateScrollViewDraggedOffsetProgress:1;
        unsigned int delegateWillTriggerRefresh:1;
        unsigned int delegateWillFinishRefresh:1;
        unsigned int delegateCanTriggered:1;
    } _implementProtocolFlags;
}
@property (nonatomic, assign)    NDRefreshControlState     state;
@property (nonatomic, assign)    BOOL                      isTrigged;

@property (nonatomic, assign)    CGFloat                   height;
@property (nonatomic, readwrite, getter=isRefreshing) BOOL refreshing;
@property (nonatomic, readwrite) CGFloat                   threshold;

@end

@implementation NDRefreshControl
@synthesize state = _state;

#pragma mark - life cycle
- (instancetype)init {
    return [self initWithThreshold:NDRefreshControlDefaultThreshold height:NDRefreshControlDefaultHeight];
}

- (instancetype)initWithThreshold:(CGFloat)threshold height:(CGFloat)height {
    self = [super initWithFrame:CGRectZero];
    if (self) {
        _threshold = threshold;
        _height = height;
        self.opaque = NO;
        NDCircleContentView *containView = [[NDCircleContentView alloc] init];
        containView.contentSize = CGSizeMake(35, 35);
        containView.contentMode = UIViewContentModeScaleAspectFit;
        containView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
        self.containView = containView;
    }
    return self;
}

- (void)dealloc {
    [self removeObserver];
}

#pragma mark - setter
- (void)setScrollView:(UIScrollView *)scrollView {
    [self removeObserver];
    _scrollView = scrollView;
    [self addObserver];
}

- (void)setFrame:(CGRect)frame {
    [super setFrame:frame];
    [self resetFrame];
}

- (void)willMoveToSuperview:(UIView *)newSuperview {
    [super willMoveToSuperview:newSuperview];
}

- (void)setState:(NDRefreshControlState)aState {
    CGFloat offset = 0;
    CGFloat percent = 0;
    if (self.position == RefreshControlPositionTop) {
        // 下拉刷新
        offset = -(self.scrollView.contentOffset.y + self.originalContentInsetY);
        offset = MIN(MAX(ceilf(offset), 0.0f), _threshold);
        
        percent = offset / _threshold;
    } else if (self.position == RefreshControlPositionBottom) {
        // 上拉加载
        
        // 偏移量
        CGFloat offsetY = self.scrollView.contentOffset.y;
        // 内容与控件差值
        CGFloat remainOffset = self.scrollView.contentSize.height - self.scrollView.frame.size.height;
        
        offset = offsetY - remainOffset;
        offset = MIN(MAX(ceilf(offset), 0.0f), _threshold);
        
        percent = offset / _threshold;
    }
    
    if (self.draggedProgressHandler) {
        self.draggedProgressHandler(percent);
    }
    
    if (_implementProtocolFlags.delegateScrollViewDraggedOffsetProgress) {
        [self.containView nd_refreshControl:self scrollViewDraggedOffset:offset progress:percent];
    }
	
	_state = aState;
}

- (void)setContainView:(UIView<NDRefreshControlContainViewDelegate> *)containView {
    BOOL canformsToProtocol = [containView conformsToProtocol:@protocol(NDRefreshControlContainViewDelegate)];
    NSAssert1(canformsToProtocol, @"设定的containView（%@）必须实现NDRefreshControlContainViewDelegate协议", containView);
    if (canformsToProtocol) {
        // remove all subviews of implement NDRefreshControlContainViewDelegate
        NSArray *canformToProtocolViews = [self.subviews bk_select:^BOOL(id obj) {
            return [obj conformsToProtocol:@protocol(NDRefreshControlContainViewDelegate)];
        }];
        [canformToProtocolViews makeObjectsPerformSelector:@selector(removeFromSuperview)];
        
        // implement flags
        _implementProtocolFlags.delegateScrollViewDraggedOffsetProgress = [containView respondsToSelector:@selector(nd_refreshControl:scrollViewDraggedOffset:progress:)];
        _implementProtocolFlags.delegateWillTriggerRefresh = [containView respondsToSelector:@selector(nd_refreshControlWillTriggerRefresh:)];
        _implementProtocolFlags.delegateWillFinishRefresh = [containView respondsToSelector:@selector(nd_refreshControlWillFinishRefresh:)];
        _implementProtocolFlags.delegateCanTriggered = [containView respondsToSelector:@selector(nd_refreshControlCanTriggered:)];
        
        [self addSubview:(UIView *)containView];
        _containView = containView;
        [self resetFrame];
    } else {
        memset(&_implementProtocolFlags, 0, sizeof(_implementProtocolFlags));
    }
}

#pragma mark - getter
- (NSTimeInterval)disappearingTimeInterval {
    if (_disappearingTimeInterval <= 0) {
        _disappearingTimeInterval = .3f;
    }
    
    return _disappearingTimeInterval;
}

- (BOOL)isRefreshing {
    return _refreshing;
}

#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    
    if (self.position == RefreshControlPositionTop) {
        if (self.scrollView.contentOffset.y + self.originalContentInsetY > 0) {
            return;
        }
    } else if (self.position == RefreshControlPositionBottom) {
        if (self.scrollView.contentSize.height < self.scrollView.frame.size.height) {
            return;
        }
    }
    
    if ([keyPath isEqualToString:@"contentOffset"]) {
        [self scrollviewContentOffsetChange:change object:object];
    } else if ([keyPath isEqualToString:@"pan.state"]) {
        [self scrollviewPanGestureStateChange:change object:object];
    } else if ([keyPath isEqualToString:@"contentSize"]) {
        [self layoutLoadMoreView];
    }
}

#pragma mark - private
- (void)addObserver {
    @try {
        [self removeObserver];
    }
    @catch (NSException *exception) {
        
    }
    @finally {
        [_scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
        [_scrollView addObserver:self forKeyPath:@"pan.state" options:NSKeyValueObservingOptionNew context:nil];
        [_scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
    }
}

- (void)removeObserver {
    @try {
        [_scrollView removeObserver:self forKeyPath:@"contentOffset"];
        [_scrollView removeObserver:self forKeyPath:@"pan.state"];
        [_scrollView removeObserver:self forKeyPath:@"contentSize"];
    }
    @catch (NSException *exception) {
    }
}

- (void)scrollviewContentOffsetChange:(NSDictionary *)change object:(id)object {
    
    CGPoint contentOffset = [[change valueForKey:NSKeyValueChangeNewKey] CGPointValue];
    if (self.position == RefreshControlPositionTop) {
        // 下拉刷新
        [self pullRefreshContentOffsetChange:contentOffset];
    } else if (self.position == RefreshControlPositionBottom) {
        // 上拉加载
        [self loadMoreContentOffsetChange:contentOffset];
    }
}

- (void)scrollviewPanGestureStateChange:(NSDictionary *)change object:(id)object {
    
    if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateEnded) {
        return;
    }
    
    if (_isTrigged) {
        if (_implementProtocolFlags.delegateCanTriggered) {
            BOOL canTriggered = [self.containView nd_refreshControlCanTriggered:self];
            if (!canTriggered) {
                return;
            }
        }
        [self triggerRefresh];
    }
}

- (void)layoutLoadMoreView {
    if (self.position != RefreshControlPositionBottom) {
        return;
    }
    
    if (!self.superview) {
        return;
    }
    
    if (self.superview != _scrollView) {
        return;
    }
    
    sSelf = self;
    
    [self removeFromSuperview];
    
    CGSize contentSize = _scrollView.contentSize;
    self.frame = CGRectMake(0, contentSize.height, contentSize.width, CGRectGetHeight(self.bounds));
    [_scrollView addSubview:self];
    
    sSelf = nil;
}

- (void)pullRefreshContentOffsetChange:(CGPoint)contentOffset {
    if (_state != NDRefreshControlStateLoading) {
        if (self.scrollView.isDragging && self.scrollView.contentOffset.y + self.originalContentInsetY < -_threshold) {
            _isTrigged = YES;
        } else {
            _isTrigged = NO;
        }
        [self setState:NDRefreshControlStateDragging];
    }
}

- (void)loadMoreContentOffsetChange:(CGPoint)contentOffset {
    if (_state != NDRefreshControlStateLoading) {
        if (self.scrollView.contentSize.height <= self.scrollView.frame.size.height) {
            _isTrigged = NO;
            return;
        }
        
        // 偏移量
        CGFloat offsetY = self.scrollView.contentOffset.y;
        // 内容与控件差值
        CGFloat remainOffset = self.scrollView.contentSize.height - self.scrollView.frame.size.height;
        
        CGFloat differenceValues = offsetY - remainOffset;
        
        if (self.scrollView.isDragging && differenceValues >= _threshold) {
            _isTrigged = YES;
        } else {
            _isTrigged = NO;
        }
        [self setState:NDRefreshControlStateDragging];
    }
}

- (void)triggerRefresh {
    if (_state == NDRefreshControlStateLoading) {
        return;
    }
    
    _refreshing = YES;
    if (_implementProtocolFlags.delegateWillTriggerRefresh) {
        [self.containView nd_refreshControlWillTriggerRefresh:self];
    }
    
    [self setState:NDRefreshControlStateLoading];
    @weakify(self);
    [UIView animateWithDuration:0.2
                          delay:0
                        options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState
                     animations:^{
                         @strongify(self);
                         if (self.position == RefreshControlPositionTop) {
                             // 下拉刷新
                             self.scrollView.contentOffset = CGPointMake(0, -_height - self.originalContentInsetY);
                             self.scrollView.contentInset = UIEdgeInsetsMake(_height + self.originalContentInsetY, 0.0f, 0.0f, 0.0f);
                         } else if (self.position == RefreshControlPositionBottom) {
                             // 上拉加载
                             CGPoint contentOffset = self.scrollView.contentOffset;
                             contentOffset.y += _height;
                             self.scrollView.contentOffset = contentOffset;
                             UIEdgeInsets contentInset = self.scrollView.contentInset;
                             contentInset.bottom = _height;
                             self.scrollView.contentInset = contentInset;
                         }
                     }
                     completion:^(BOOL finished) {
                         @strongify(self);
                         if (self.refreshActionHandler) {
                             self.refreshActionHandler(self);
                         }
                     }];
}

- (void)resetFrame {
    ((UIView *)self.containView).frame = self.bounds;
}

#pragma mark - public
- (void)beginRefreshing {
    if (!self.isRefreshing) {
        [self triggerRefresh];
        [self sendActionsForControlEvents:UIControlEventValueChanged];
    }
}

- (void)endRefreshing {
    if (_state == NDRefreshControlStateLoading) {
        _isTrigged = NO;
        _refreshing = NO;
        
        [self setState:NDRefreshControlStateDragging];
        
        if (_implementProtocolFlags.delegateWillFinishRefresh) {
            [self.containView nd_refreshControlWillFinishRefresh:self];
        }
        @weakify(self);
        
        [UIView animateWithDuration:self.disappearingTimeInterval
                              delay:0
                            options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState
                         animations:^{
                             @strongify(self);
                             dispatch_block_t performBlock = ^{
                                 @strongify(self);
                                 self.scrollView.contentInset = UIEdgeInsetsMake(self.originalContentInsetY, 0.0f, 0.0f, 0.0f);
                             };
                             if (_position == RefreshControlPositionBottom) {
                                 [self bk_performBlock:^(id obj) {
                                     performBlock();
                                 } afterDelay:.03f];
                             } else {
                                 performBlock();
                             }
                         }
                         completion:nil];
    }
}

@end